package com.nulldev.util.web.flexibleKit.scfk.httpclient;

import java.net.URL;
import java.time.Duration;
import java.util.List;
import java.util.Map;

import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.HttpClient.HttpMethod;
import com.nulldev.util.web.HttpClient.HttpRequest;
import com.nulldev.util.web.HttpClient.HttpResponse;
import com.nulldev.util.web.HttpClient.ResponseFormat;
import com.nulldev.util.web.URLUtils.Entry;
import com.nulldev.util.web.flexibleKit.scfk.HTTPRequestType;
import com.nulldev.util.web.flexibleKit.scfk.SCFKRequest;
import com.nulldev.util.web.flexibleKit.scfk.io.streams.Stream;
import com.nulldev.util.web.flexibleKit.scfk.io.streams.StreamGenerator;
import com.nulldev.util.web.flexibleKit.scfk.protocols.http1.Http1Request;

public abstract class SCFKClientRequest<T> implements HttpRequest<T> {

	private final SCFKRequest r = new SCFKRequest();
	private static final Logger log = LoggerFactory.getLogger();

	@Override
	public HttpRequest<T> url(String url) throws Exception {
		this.r.updateURL(com.nulldev.util.web.URLUtils.fparser.URL.parse(url));
		return this;
	}

	@Override
	public HttpRequest<T> url(URL url) throws Exception {
		this.r.updateURL(com.nulldev.util.web.URLUtils.fparser.URL.parse(url.toExternalForm()));
		return null;
	}

	@Override
	public HttpRequest<T> header(String key, String value) {
		this.r.header(key, value);
		return this;
	}

	@Override
	public HttpRequest<T> header(Entry entry) {
		this.r.header(entry);
		return this;
	}

	@Override
	public HttpRequest<T> headers(Entry[] entries) {
		if (entries == null || entries.length == 0)
			return this;
		for (final Entry e : entries) {
			if (e == null)
				continue;
			this.r.header(e);
		}
		return this;
	}

	@Override
	public HttpRequest<T> forceHeader(String key, String value) {
		this.r.header(key, value);
		return this;
	}

	@Override
	public HttpRequest<T> timeout(Duration duration) {
		return this;
	}

	@Override
	public HttpResponse<T> create(HttpMethod method, Object body) throws Exception {
		this.r.setRequestType(HTTPRequestType.from(method));
		this.r.setBody(body);
		final Stream s = StreamGenerator.autoStream(r);
		return new SCFKClientResponse<T>(this.getFormat(), new Http1Request().onRequestWrite(this.r, s));
	}

	@Override
	public Executable<HttpResponse<T>> createTask(HttpMethod method, Object body) throws Exception {
		this.r.setRequestType(HTTPRequestType.from(method));
		this.r.setBody(body);
		return ExecutorManager.make(() -> {
			Stream s = null;
			try {
				s = StreamGenerator.autoStream(r);
				return new SCFKClientResponse<T>(this.getFormat(), new Http1Request().onRequestWrite(this.r, s));
			} catch (Exception e) {
				log.error("Failed to handle task for stream: " + s);
				IOUtils.closeQuietly(s);
				throw new RuntimeException(e);
			}
		});
	}

	@Override
	public Executable<HttpResponse<T>> createTaskAndRun(HttpMethod method, Object body) throws Exception {
		return ExecutorManager.get().asyncSubmit(this.createTask(method, body));
	}

	@Override
	public boolean isSupportedMethod(final HttpMethod method) {
		return method == HttpMethod.GET || method == HttpMethod.POST || method == HttpMethod.OPTIONS || method == HttpMethod.HEAD || method == HttpMethod.PUT;
	}

	@Override
	public HttpRequest<T> enableBuffering(int bufferSize) {
		return this;
	}

	@Override
	public abstract ResponseFormat getFormat();

	@Override
	public boolean isFeatureFlagSupported(final int feature) {
		switch (feature) {
			case FEATURE_FLAG_REQUEST_READ_API:
				return true;
			default:
				return false;
		}
	}

	/* Read API */

	@Override
	public String url() {
		return this.r.getURL().toString();
	}

	@Override
	public Map<String, List<String>> headers() {
		final Map<String, List<String>> output = FastMaps.map();
		for (final Map.Entry<String, String> e : this.r.headers().entrySet())
			output.put(e.getKey(), Lists.of(e.getValue()));
		return output;
	}
}
